package com.ruben.simpleboot

import cn.hutool.core.util.ReflectUtil
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper
import com.baomidou.mybatisplus.core.metadata.OrderItem
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils
import com.baomidou.mybatisplus.core.toolkit.Wrappers
import com.baomidou.mybatisplus.core.toolkit.support.SFunction
import com.baomidou.mybatisplus.extension.toolkit.SimpleQuery
import com.ruben.simpleboot.adapter.RecycleBinAdapter
import com.ruben.simpleboot.interceptor.RecycleBinLineHandler
import com.ruben.simpleboot.interceptor.RecycleThreadLocal
import com.ruben.simpleboot.pojo.common.BaseRecycleEntity
import com.ruben.simpleboot.pojo.dto.RecycleBinDTO
import com.ruben.simpleboot.pojo.po.RoleInfo
import com.ruben.simpleboot.pojo.po.UserInfo
import com.ruben.simpleboot.pojo.po.UserRole
import io.github.vampireachao.stream.core.optional.Opp
import io.github.vampireachao.stream.core.stream.Steam
import io.github.vampireachao.stream.plugin.mybatisplus.Database
import io.github.vampireachao.stream.plugin.mybatisplus.One
import io.github.vampireachao.stream.plugin.mybatisplus.OneToManyToOne
import org.apache.ibatis.logging.LogFactory
import org.apache.ibatis.reflection.property.PropertyNamer
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.jdbc.core.JdbcTemplate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.function.Consumer
import java.util.function.UnaryOperator

@SpringBootTest
class RecycleBinTest {

    @BeforeEach
    fun clear() {
        RecycleThreadLocal.ONLY_SHOW_RECYCLE.set(false)
        RecycleThreadLocal.REAL_DELETE.set(false)
    }

    @Test
    fun onlyShowNotRecycle() {
        // 默认查询没回收的
        val userIdRolesMap = OneToManyToOne
            .of<UserRole, Long, String, RoleInfo>(
                UserRole::getUserId
            ).eq(1L)
            .value(UserRole::getRoleId).attachKey(RoleInfo::getId).query()
        Assertions.assertFalse(userIdRolesMap[1L]?.isEmpty() ?: false)
    }

    @Test
    fun onlyShowRecycle() {
        // 查询回收了的
        RecycleThreadLocal.ONLY_SHOW_RECYCLE.set(true)
        val userIdRolesMap = OneToManyToOne
            .of<UserRole, Long, String, RoleInfo>(
                UserRole::getUserId
            ).eq(1L)
            .value(UserRole::getRoleId).attachKey(RoleInfo::getId).query()
        Assertions.assertTrue(userIdRolesMap[1L]?.isEmpty() ?: false)
    }

    @Test
    fun recycleLogicDeleteTest() {
        // 默认删除变为回收数据
        Database.removeById(1L, UserInfo::class.java)
        Assertions.assertNull(One.of(UserInfo::getId).eq(1L).query())
        // 查询回收站
        RecycleThreadLocal.ONLY_SHOW_RECYCLE.set(true)
        Assertions.assertNotNull(One.of(UserInfo::getId).eq(1L).query())
    }

    @Test
    fun recycleTest() {
        // 默认删除变为回收数据
        Database.removeById("1", RoleInfo::class.java)
        Assertions.assertNull(One.of(RoleInfo::getId).eq("1").query())
        // 查询回收站
        RecycleThreadLocal.ONLY_SHOW_RECYCLE.set(true)
        Assertions.assertNotNull(One.of(RoleInfo::getId).eq("1").query())
    }

    @Test
    fun restoreTest() {
        // 还原数据
        Database.removeById(1L, UserInfo::class.java)
        Assertions.assertNull(One.of(UserInfo::getId).eq(1L).query())
        // 查询回收站
        RecycleThreadLocal.ONLY_SHOW_RECYCLE.set(true)
        // 还原数据
        Database.updateById(UserInfo().apply {
            id = 1L;gmtRecycled =
            LocalDateTime.parse(
                RecycleBinLineHandler.NOT_RECYCLE_VALUE,
                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
            )
        })
        RecycleThreadLocal.ONLY_SHOW_RECYCLE.set(false)
        Assertions.assertNotNull(One.of(UserInfo::getId).eq(1L).query())
    }

    @Test
    fun deleteTest() {
        // 彻底删除
        RecycleThreadLocal.REAL_DELETE.set(true)
        Database.removeById(1L, UserInfo::class.java)
        // 查询未回收
        Assertions.assertNull(One.of(UserInfo::getId).eq(1L).query())
        // 查询回收站
        RecycleThreadLocal.ONLY_SHOW_RECYCLE.set(true)
        Assertions.assertNull(One.of(UserInfo::getId).eq(1L).query())
    }


    val dto = RecycleBinDTO().apply {
        size = 2
        adapters = listOf(
            RecycleBinAdapter<UserInfo>().apply {
                clazz = UserInfo::class.java
                condition = UnaryOperator { it.ge(UserInfo::getAge, 0) }
                dataListResolver = Consumer { it.forEach { i -> i.recycleName = i.name } }
                orderColumnHandler = UnaryOperator {
                    when (it) {
                        "recycleName" -> getColumn(UserInfo::getName)
                        "recycleSort" -> TableInfoHelper.getTableInfo(UserInfo::class.java).keyColumn
                        else -> {
                            LogFactory.getLog(this.javaClass).warn("未知的排序字段 $it")
                            null
                        }
                    }
                }
            },
            RecycleBinAdapter<RoleInfo>().apply {
                clazz = RoleInfo::class.java
                dataListResolver = Consumer { it.forEach { i -> i.recycleName = i.roleName } }
                orderColumnHandler = UnaryOperator {
                    when (it) {
                        "recycleName" -> getColumn(RoleInfo::getRoleName)
                        "recycleSort" -> TableInfoHelper.getTableInfo(RoleInfo::class.java).keyColumn
                        else -> {
                            LogFactory.getLog(this.javaClass).warn("未知的排序字段 $it")
                            null
                        }
                    }
                }
            }
        )
        orders = listOf(
            OrderItem().apply {
                column = "recycleSort"
                isAsc = true
            },
            OrderItem().apply {
                column = "gmtRecycled"
                isAsc = false
            },
            OrderItem().apply {
                column = "recycleName"
                isAsc = false
            }
        )
    }

    @Test
    fun testNextPage() {
        println("第一页 ${nextPage(dto)}")
        println("第二页 ${nextPage(dto)}")
        println("第三页 ${nextPage(dto)}")
        println("第四页 ${nextPage(dto)}")
    }

    @Test
    fun testFixedPage() {
        println("第一页 ${fixedPage(dto)}")
        dto.current = 2
        println("第二页 ${fixedPage(dto)}")
        dto.current = 3
        println("第三页 ${fixedPage(dto)}")
        dto.current = 4
        println("第四页 ${fixedPage(dto)}")
    }

    @Test
    fun resolveMemoryOrdersTest() {
        val list = Steam.of(
            UserInfo().apply { recycleSort = 2;gmtRecycled = LocalDateTime.parse("2022-10-11T10:39:51") },
            UserInfo().apply { recycleSort = 2;gmtRecycled = LocalDateTime.parse("2022-10-11T10:39:52") },
            UserInfo().apply { recycleSort = 1 },
        ).sorted(
            resolveMemoryOrders(
                listOf(
                    OrderItem("recycleSort", true),
                    OrderItem("gmtRecycled", false),
                )
            )
        )
            .toList()
        Assertions.assertEquals(1, list[0].recycleSort)
    }

    fun getPropertyColumnMap(clazz: Class<*>): Map<String, String> {
        return TableInfoHelper.getTableInfo(clazz).fieldList.associateBy({ it.property }, { it.column })
    }

    fun <T, R> getColumn(property: SFunction<T, R>): String? {
        val clazz = SimpleQuery.getType(property)
        return getPropertyColumnMap(clazz).get(PropertyNamer.methodToProperty(LambdaUtils.extract(property).implMethodName))
    }

    fun nextPage(dto: RecycleBinDTO): RecycleBinDTO {
        dto.adapters.forEachIndexed { idx, it -> it.idx = idx }
        dto.total = Steam.of(dto.adapters).parallel().reduce(0L, { acc, it ->
            val wrapper = Wrappers.query(ReflectUtil.newInstance(it.clazz!!)) as QueryWrapper<out BaseRecycleEntity>
            val lambdaWrapper =
                (it.condition as UnaryOperator<LambdaQueryWrapper<out BaseRecycleEntity>>).apply(wrapper.lambda())
            it.offset = dto.offsets?.get(it.idx) ?: 0
            val count = Database.count(lambdaWrapper)
            // 处理排序
            resolveQueryOrders(dto.orders(), it, wrapper)
            val dataList: List<BaseRecycleEntity> = if (count > it.offset)
                Database.list(lambdaWrapper.last("limit ${it.offset},${dto.size}"))
                    .onEach { e -> e.adapter = it } else ArrayList()
            val dataListResolver = it.dataListResolver as Consumer<List<BaseRecycleEntity>>
            dataListResolver.accept(dataList)
            it.dataList = dataList
            acc + count
        }, { acc, it -> acc + it })
        dto.records = Steam.of(dto.adapters).flat { it.dataList }
            .sorted(resolveMemoryOrders(dto.orders()))
            .limit(dto.size)
            .peek { it.adapter.offset++ }.toList()
        dto.offsets = dto.adapters.map { it.offset }
        return dto
    }

    private fun resolveQueryOrders(
        orders: List<OrderItem>,
        it: RecycleBinAdapter<*>,
        wrapper: QueryWrapper<out BaseRecycleEntity>
    ) {
        var hasOrders = false
        orders.forEach { order ->
            val column = getPropertyColumnMap(it.clazz)[order.column] ?: it.orderColumnHandler.apply(order.column)
            if (column != null) {
                hasOrders = true
                wrapper.orderBy(true, order.isAsc, column)
            }
        }
        if (!hasOrders) {
            wrapper.orderBy(true, true, TableInfoHelper.getTableInfo(it.clazz!!).keyColumn)
        }
    }

    private fun resolveMemoryOrders(
        orders: List<OrderItem>,
    ): Comparator<in BaseRecycleEntity> {
        if (orders.isEmpty()) return Comparator { _, _ -> 1 }
        var comparator: Comparator<BaseRecycleEntity>? = Comparator.comparing { it.javaClass.toString() }
        Steam.of(orders).forEach { order ->
            var comparing = Comparator.comparing<BaseRecycleEntity, Comparable<Any>>(
                { e -> Opp.of(ReflectUtil.getFieldValue(e, order.column)).map { it as Comparable<Any> }.get() },
                { o1, o2 -> if (o1 == null || o2 == null) 1 else o1.compareTo(o2) }
            )
            if (!order.isAsc) {
                comparing = comparing.reversed()
            }
            comparator = comparator!!.thenComparing(comparing)
        }
        return comparator!!
    }

    fun fixedPage(dto: RecycleBinDTO): RecycleBinDTO {
        Steam.of(dto.adapters).parallel().reduce(0L, { acc, it ->
            val wrapper = Wrappers.query(ReflectUtil.newInstance(it.clazz!!)) as QueryWrapper<out BaseRecycleEntity>
            val lambdaWrapper =
                (it.condition as UnaryOperator<LambdaQueryWrapper<out BaseRecycleEntity>>).apply(wrapper.lambda())
            val maxLimit = dto.current * dto.size
            val count = Database.count(lambdaWrapper)
            // 处理排序
            resolveQueryOrders(dto.orders(), it, wrapper)
            val dataList: List<BaseRecycleEntity> = if (maxLimit > 0 && count > 0)
                Database.list(lambdaWrapper.last("limit 0,${maxLimit}"))
                    .onEach { e ->
                        e.adapter = it
                        if (e.recycleSort == null) {
                            e.recycleSort = Opp.of(
                                ReflectUtil.getFieldValue(
                                    it,
                                    TableInfoHelper.getTableInfo(it.clazz).keyProperty
                                )
                            )
                                .map { f -> f.toString() }
                                .map { s -> s.toLongOrNull() }
                                .get()
                        }
                    } else ArrayList()
            val dataListResolver = it.dataListResolver as Consumer<List<BaseRecycleEntity>>
            dataListResolver.accept(dataList)
            it.dataList = dataList
            acc + count
        }, { acc, it -> acc + it })

        dto.records = Steam.of(dto.adapters)
            .flat { it.dataList }
            .sorted(resolveMemoryOrders(dto.orders()))
            .skip((dto.current - 1) * dto.size)
            .limit(dto.size)
            .toList()
        return dto
    }

    @Test
    fun testH2OrderBy(@Autowired template: JdbcTemplate) {
        Assertions.assertDoesNotThrow { template.query("select * from user_info order by gmt_recycled desc , name desc") {} }
        Assertions.assertThrows(Exception::class.java) { template.query("select count(*) from user_info order by gmt_recycled desc, name desc") {} }
    }

}
